using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Collections;

using SBPweb.Controls.Windows.Editors.HelperClasses;

namespace SBPweb.Controls.Windows.Editors
{
	[ToolboxBitmap(typeof(System.Windows.Forms.RichTextBox))]
	public class ExtendedRichTextBox : RichTextBox
	{
		public const int MaxNumberOfEmoticons = 10;

		#region Private fields

		private ImageCollection smileys = new ImageCollection();
		private Control fakeControl = new Control();
		private Font pCurrentFont;
		private Color pCurrentColor;

		#endregion

		#region Constructor logic
		public ExtendedRichTextBox()
		{
            base.SetStyle(ControlStyles.AllPaintingInWmPaint, false);
            base.SetStyle(ControlStyles.Opaque, false);
            //base.SetStyle(ControlStyles.ResizeRedraw, true);
			//base.SetStyle(ControlStyles.DoubleBuffer, true);
			base.SetStyle(ControlStyles.SupportsTransparentBackColor, false);

			fakeControl.Size = new Size(0, 0);
			this.Controls.Add(fakeControl);
		}
		#endregion

		#region Public properties
		[Category("Smiley support"),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public ImageCollection Emoticons
		{
			get
			{
				return smileys;
			}
			set
			{
				smileys = value;
			}
		}
		#endregion

		#region Overriden members
		
        //override protected CreateParams CreateParams
        //{
        //    get
        //    {
        //        CreateParams cp = base.CreateParams;
        //        cp.ExStyle |= 0x20;
        //        return cp;
        //    }
        //}

		[STAThread]
		public new void AppendText(string text)
		{
			#region Old code

			/*
			string[] keys = (string[])((new ArrayList(Emoticons.Keys)).ToArray(typeof(string)));
			string[] tokens;
			if (keys.Length == 0)
			{
				tokens = new string[1];
				tokens[0] = text;
			}
			else tokens = text.Split(keys, StringSplitOptions.None);

			int pos = 0;
			int i = 0;
			int countOfEmoticons = 0;

			foreach (string token in tokens)
			{
				pos += token.Length;
				string s = text.Substring(pos);
				bool b = false;
				if (++i < tokens.Length)
				{
					string nextToken = tokens[i];
					if (nextToken.Length > 0)
					{
						s = s.Substring(0, s.IndexOf(nextToken));
						pos += s.Length;
					}
					else
					{
						//if nextToken is empty..
						//it's ugly but works
						bool flag = true;

						for (int j = 1; j < s.Length && flag; j++)
						{
							string ss = s.Substring(0, j);
							for (int k = 0; k < keys.Length && flag; k++)
								if (keys[k].Equals(ss))
								{
									flag = false;

									pos += ss.Length;
									s = ss;
								}
						}
					}
					b = true;
				}

                if (token != string.Empty)
                    base.AppendText(token);

				if (b)
				{
					if (countOfEmoticons < MaxNumberOfEmoticons)
					{
						countOfEmoticons++;
						try
						{
							PictureBox pb = new PictureBox();

							fakeControl.Controls.Add(pb);
							pb.Image = Emoticons[s];
							pb.SizeMode = PictureBoxSizeMode.AutoSize;
							pb.BackColor = this.BackColor;

							this.InsertControl(pb);
						}
						catch
							(Exception ex)
						{
							//Todo: Rewrite it!!!!!
						}
					}
					else
					{
						base.AppendText(s);
					}
				}
			}

			//this.Size = new Size(this.Width, EstimateExtendedHeight());
			*/
			#endregion

			string s = text;
			string[] keys = (string[])((new ArrayList(Emoticons.Keys)).ToArray(typeof(string)));
			int countOfEmoticons = 0;

			bool imageFound = false;
			while (s.Length > 0)
			{
				foreach (string key in keys)
				{
					imageFound = false;
					if (s.StartsWith(key))
					{
						if (countOfEmoticons < MaxNumberOfEmoticons)
						{
							countOfEmoticons++;
							try
							{
								PictureBox pb = new PictureBox();

								fakeControl.Controls.Add(pb);
								pb.Image = Emoticons[key];
								pb.SizeMode = PictureBoxSizeMode.AutoSize;
								pb.BackColor = this.BackColor;

								this.InsertControl(pb);
								s = s.Substring(key.Length);
								imageFound = true;
								break;
							}
							catch 
							{ 
							}
						}
					}
				}
				if (!imageFound)
				{
					if (s.Length > 0)
					{
						DoSetStyle();
						base.AppendText(s.Substring(0, 1));
						s = s.Substring(1);
					}
				}
			}
		}

		#endregion

		#region Public methods

		public int CountOfEmoticons(string text)
		{
			int count = 0;

			string[] keys = (string[])((new ArrayList(Emoticons.Keys)).ToArray(typeof(string)));
			string[] tokens;
			if (keys.Length == 0)
			{
				tokens = new string[1];
				tokens[0] = text;
			}
			else tokens = text.Split(keys, StringSplitOptions.None);

			int pos = 0;
			int i = 0;

			foreach (string token in tokens)
			{
				pos += token.Length;
				string s = text.Substring(pos);
				bool b = false;
				if (++i < tokens.Length)
				{
					string nextToken = tokens[i];
					if (nextToken.Length > 0)
					{
						s = s.Substring(0, s.IndexOf(nextToken));
						pos += s.Length;
					}
					else
					{
						//if nextToken is empty..
						//it's ugly but works
						bool flag = true;

						for (int j = 1; j < s.Length && flag; j++)
						{
							string ss = s.Substring(0, j);
							for (int k = 0; k < keys.Length && flag; k++)
								if (keys[k].Equals(ss))
								{
									flag = false;

									pos += ss.Length;
									s = ss;
								}
						}
					}
					b = true;
				}

				if (b)
				{
					count++;
				}
			}

			return count;
		}

		public int EstimateExtendedHeight()
		{
			return this.GetPositionFromCharIndex(this.Text.Length - 1).Y + 3 * this.FontHeight;
		}

		public void SetStyle(Font font, Color color)
		{
			pCurrentFont = font;
			pCurrentColor = color;
		}

		#endregion

		#region Imports and structs

		// It makes no difference if we use PARAFORMAT or
		// PARAFORMAT2 here, so I have opted for PARAFORMAT2.
		[StructLayout(LayoutKind.Sequential)]
		public struct PARAFORMAT
		{
			public int cbSize;
			public uint dwMask;
			public short wNumbering;
			public short wReserved;
			public int dxStartIndent;
			public int dxRightIndent;
			public int dxOffset;
			public short wAlignment;
			public short cTabCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
			public int[] rgxTabs;

			// PARAFORMAT2 from here onwards.
			public int dySpaceBefore;
			public int dySpaceAfter;
			public int dyLineSpacing;
			public short sStyle;
			public byte bLineSpacingRule;
			public byte bOutlineLevel;
			public short wShadingWeight;
			public short wShadingStyle;
			public short wNumberingStart;
			public short wNumberingStyle;
			public short wNumberingTab;
			public short wBorderSpace;
			public short wBorderWidth;
			public short wBorders;
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct CHARFORMAT
		{
			public int cbSize;
			public UInt32 dwMask;
			public UInt32 dwEffects;
			public Int32 yHeight;
			public Int32 yOffset;
			public Int32 crTextColor;
			public byte bCharSet;
			public byte bPitchAndFamily;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
			public char[] szFaceName;

			// CHARFORMAT2 from here onwards.
			public short wWeight;
			public short sSpacing;
			public Int32 crBackColor;
			public uint lcid;
			public uint dwReserved;
			public short sStyle;
			public short wKerning;
			public byte bUnderlineType;
			public byte bAnimation;
			public byte bRevAuthor;
			public byte bReserved1;
		}

		[DllImport("user32", CharSet = CharSet.Auto)]
		private static extern int SendMessage(HandleRef hWnd,
			int msg,
			int wParam,
			int lParam);

		[DllImport("user32", CharSet = CharSet.Auto)]
		private static extern int SendMessage(HandleRef hWnd,
			int msg,
			int wParam,
			ref PARAFORMAT lp);

		[DllImport("user32", CharSet = CharSet.Auto)]
		private static extern int SendMessage(HandleRef hWnd,
			int msg,
			int wParam,
			ref CHARFORMAT lp);

		private const int EM_SETEVENTMASK = 1073;
		private const int WM_SETREDRAW = 11;

		[DllImport("User32.dll", CharSet = CharSet.Auto, PreserveSig = false)]
		public static extern IRichEditOle SendMessage(IntPtr hWnd, int message, int wParam);

		[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
		internal static extern bool GetClientRect(IntPtr hWnd, [In, Out] ref Rectangle rect);

		[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
		internal static extern bool GetWindowRect(IntPtr hWnd, [In, Out] ref Rectangle rect);

		[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
		internal static extern IntPtr GetParent(IntPtr hWnd);

		[DllImport("ole32.dll")]
		static extern int OleSetContainedObject([MarshalAs(UnmanagedType.IUnknown)]
			object pUnk, bool fContained);

		[DllImport("ole32.dll")]
		static extern int OleLoadPicturePath(
			[MarshalAs(UnmanagedType.LPWStr)] string lpszPicturePath,
			[MarshalAs(UnmanagedType.IUnknown)][In] object pIUnknown,
			uint dwReserved,
			uint clrReserved,
			ref Guid riid,
			[MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

		[DllImport("ole32.dll")]
		static extern int OleCreateFromFile([In] ref Guid rclsid,
			[MarshalAs(UnmanagedType.LPWStr)] string lpszFileName, [In] ref Guid riid,
			uint renderopt, ref FORMATETC pFormatEtc, IOleClientSite pClientSite,
			IStorage pStg, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

		[DllImport("ole32.dll")]
		static extern int OleCreateFromData(SBPweb.Controls.Windows.Editors.HelperClasses.IDataObject pSrcDataObj,
			[In] ref Guid riid, uint renderopt, ref FORMATETC pFormatEtc,
			IOleClientSite pClientSite, IStorage pStg,
			[MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

		[DllImport("ole32.dll")]
		static extern int OleCreateStaticFromData([MarshalAs(UnmanagedType.Interface)]SBPweb.Controls.Windows.Editors.HelperClasses.IDataObject pSrcDataObj,
			[In] ref Guid riid, uint renderopt, ref FORMATETC pFormatEtc,
			IOleClientSite pClientSite, IStorage pStg,
			[MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

		[DllImport("ole32.dll")]
		static extern int OleCreateLinkFromData([MarshalAs(UnmanagedType.Interface)]SBPweb.Controls.Windows.Editors.HelperClasses.IDataObject pSrcDataObj,
			[In] ref Guid riid, uint renderopt, ref FORMATETC pFormatEtc,
			IOleClientSite pClientSite, IStorage pStg,
			[MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

		#endregion

		#region Insertion methods

		public void InsertOleObject(IOleObject oleObj)
		{
			RichEditOle ole = new RichEditOle(this);
			ole.InsertOleObject(oleObj);
		}

		public void InsertControl(Control control)
		{
			RichEditOle ole = new RichEditOle(this);
			ole.InsertControl(control);
		}

		public void InsertMyDataObject(MyDataObject mdo)
		{
			RichEditOle ole = new RichEditOle(this);
			ole.InsertMyDataObject(mdo);
		}

		public void UpdateObjects()
		{
			RichEditOle ole = new RichEditOle(this);
			ole.UpdateObjects();
		}

		public void InsertImage(Image image)
		{
			MyDataObject mdo = new MyDataObject();

			mdo.SetImage(image);

			this.InsertMyDataObject(mdo);
		}

		public void InsertImage(string imageFile)
		{
			MyDataObject mdo = new MyDataObject();

			mdo.SetImage(imageFile);

			this.InsertMyDataObject(mdo);
		}

		public void InsertImageFromFile(string strFilename)
		{
			RichEditOle ole = new RichEditOle(this);
			ole.InsertImageFromFile(strFilename);
		}

		public void InsertActiveX(string strProgID)
		{
			Type t = Type.GetTypeFromProgID(strProgID);
			if (t == null)
				return;

			object o = System.Activator.CreateInstance(t);

			bool b = (o is IOleObject);

			if (b)
				this.InsertOleObject((IOleObject)o);
		}

		#endregion

		#region Private methods

		private void DoSetStyle()
		{
			this.SelectionLength = 0;
			this.SelectionStart = this.Text.Length;
			this.SelectionColor = pCurrentColor;
			this.SelectionFont = pCurrentFont;
			this.BackColor = SystemColors.Window;
		}

		private void InitializeComponent()
		{
			//this.SuspendLayout();
			//this.ResumeLayout(false);

		}

		#endregion

		#region RichEditOle class (wrapper and helper)

		class RichEditOle
		{
			public const int WM_USER = 0x0400;
			public const int EM_GETOLEINTERFACE = WM_USER + 60;

			private ExtendedRichTextBox _richEdit;
			private IRichEditOle _RichEditOle;

			public RichEditOle(ExtendedRichTextBox richEdit)
			{
				this._richEdit = richEdit;
			}

			private IRichEditOle IRichEditOle
			{
				get
				{
					if (this._RichEditOle == null)
					{
						this._RichEditOle = SendMessage(this._richEdit.Handle, EM_GETOLEINTERFACE, 0);
					}

					return this._RichEditOle;
				}
			}


			/*[DllImport("ole32.dll", PreserveSig = false)]
			internal static extern int CreateILockBytesOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, [Out] out ILockBytes ppLkbyt);
			*/
			[DllImport("ole32.dll",
				EntryPoint = "CreateILockBytesOnHGlobal",
				ExactSpelling = true, PreserveSig = true, CharSet = CharSet.Ansi,
				CallingConvention = CallingConvention.StdCall)]
			internal static extern int CreateILockBytesOnHGlobal(IntPtr /* HGLOBAL */ hGlobal, bool fDeleteOnRelease, [Out] [MarshalAs(UnmanagedType.Interface)]out object /* ILockBytes** */ ppLkbyt);

			[DllImport("ole32.dll", CallingConvention = CallingConvention.Cdecl)]
			static extern int StgCreateDocfileOnILockBytes(ILockBytes plkbyt, uint grfMode,
				uint reserved, out IStorage ppstgOpen);

			public void InsertControl(Control control)
			{
				if (control == null)
					return;

				Guid guid = Marshal.GenerateGuidForType(control.GetType());

				//-----------------------
				ILockBytes pLockBytes;
				Object pObject;

				CreateILockBytesOnHGlobal(IntPtr.Zero, true, out pObject);
				pLockBytes = pObject as ILockBytes;

				IStorage pStorage;
				StgCreateDocfileOnILockBytes(pLockBytes, (uint)(STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE | STGM.STGM_READWRITE), 0, out pStorage);

				IOleClientSite pOleClientSite;
				this.IRichEditOle.GetClientSite(out pOleClientSite);
				//-----------------------

				//-----------------------
				REOBJECT reoObject = new REOBJECT();

				reoObject.cp = this._richEdit.TextLength;

				reoObject.clsid = guid;
				reoObject.pstg = pStorage;
				reoObject.poleobj = Marshal.GetIUnknownForObject(control);
				reoObject.polesite = pOleClientSite;
				reoObject.dvAspect = (uint)(DVASPECT.DVASPECT_CONTENT);
				reoObject.dwFlags = (uint)(REOOBJECTFLAGS.REO_BELOWBASELINE);
				reoObject.dwUser = 1;

				this.IRichEditOle.InsertObject(reoObject);
				//-----------------------

				//-----------------------
				Marshal.ReleaseComObject(pLockBytes);
				Marshal.ReleaseComObject(pOleClientSite);
				Marshal.ReleaseComObject(pStorage);
				//-----------------------
			}

			public bool InsertImageFromFile(string strFilename)
			{
				//-----------------------
				ILockBytes pLockBytes;
				Object pObject;

				CreateILockBytesOnHGlobal(IntPtr.Zero, true, out pObject);
				pLockBytes = pObject as ILockBytes;

				IStorage pStorage;
				StgCreateDocfileOnILockBytes(pLockBytes, (uint)(STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE | STGM.STGM_READWRITE), 0, out pStorage);

				IOleClientSite pOleClientSite;
				this.IRichEditOle.GetClientSite(out pOleClientSite);
				//-----------------------


				//-----------------------
				FORMATETC formatEtc = new FORMATETC();

				formatEtc.cfFormat = 0;
				formatEtc.ptd = IntPtr.Zero;
				formatEtc.dwAspect = DVASPECT.DVASPECT_CONTENT;
				formatEtc.lindex = -1;
				formatEtc.tymed = TYMED.TYMED_NULL;

				Guid IID_IOleObject = new Guid("{00000112-0000-0000-C000-000000000046}");
				Guid CLSID_NULL = new Guid("{00000000-0000-0000-0000-000000000000}");

				object pOleObjectOut;

				// I don't sure, but it appears that this function only loads from bitmap
				// You can also try OleCreateFromData, OleLoadPictureIndirect, etc.
				int hr = OleCreateFromFile(ref CLSID_NULL, strFilename, ref IID_IOleObject, (uint)OLERENDER.OLERENDER_DRAW, ref formatEtc, pOleClientSite, pStorage, out pOleObjectOut);

				if (pOleObjectOut == null)
				{
					Marshal.ReleaseComObject(pLockBytes);
					Marshal.ReleaseComObject(pOleClientSite);
					Marshal.ReleaseComObject(pStorage);

					return false;
				}

				IOleObject pOleObject = (IOleObject)pOleObjectOut;
				//-----------------------


				//-----------------------
				Guid guid = new Guid();

				//guid = Marshal.GenerateGuidForType(pOleObject.GetType());
				pOleObject.GetUserClassID(ref guid);
				//-----------------------

				//-----------------------
				OleSetContainedObject(pOleObject, true);

				REOBJECT reoObject = new REOBJECT();

				reoObject.cp = this._richEdit.TextLength;

				reoObject.clsid = guid;
				reoObject.pstg = pStorage;
				reoObject.poleobj = Marshal.GetIUnknownForObject(pOleObject);
				reoObject.polesite = pOleClientSite;
				reoObject.dvAspect = (uint)(DVASPECT.DVASPECT_CONTENT);
				reoObject.dwFlags = (uint)(REOOBJECTFLAGS.REO_BELOWBASELINE);
				reoObject.dwUser = 0;

				this.IRichEditOle.InsertObject(reoObject);
				//-----------------------

				//-----------------------
				Marshal.ReleaseComObject(pLockBytes);
				Marshal.ReleaseComObject(pOleClientSite);
				Marshal.ReleaseComObject(pStorage);
				Marshal.ReleaseComObject(pOleObject);
				//-----------------------

				return true;
			}

			public void InsertMyDataObject(MyDataObject mdo)
			{
				if (mdo == null)
					return;

				//-----------------------
				ILockBytes pLockBytes;
				Object pObject;

				int sc = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out pObject);
				pLockBytes = pObject as ILockBytes;

				IStorage pStorage;
				sc = StgCreateDocfileOnILockBytes(pLockBytes, (uint)(STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE | STGM.STGM_READWRITE), 0, out pStorage);

				IOleClientSite pOleClientSite;
				this.IRichEditOle.GetClientSite(out pOleClientSite);
				//-----------------------

				Guid guid = Marshal.GenerateGuidForType(mdo.GetType());

				Guid IID_IOleObject = new Guid("{00000112-0000-0000-C000-000000000046}");
				Guid IID_IDataObject = new Guid("{0000010e-0000-0000-C000-000000000046}");
				Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");

				object pOleObject;

				int hr = OleCreateStaticFromData(mdo, ref IID_IOleObject, (uint)OLERENDER.OLERENDER_FORMAT, ref mdo.mpFormatetc, pOleClientSite, pStorage, out pOleObject);

				if (pOleObject == null)
					return;
				//-----------------------


				//-----------------------
				OleSetContainedObject(pOleObject, true);

				REOBJECT reoObject = new REOBJECT();

				reoObject.cp = this._richEdit.TextLength;

				reoObject.clsid = guid;
				reoObject.pstg = pStorage;
				reoObject.poleobj = Marshal.GetIUnknownForObject(pOleObject);
				reoObject.polesite = pOleClientSite;
				reoObject.dvAspect = (uint)(DVASPECT.DVASPECT_CONTENT);
				reoObject.dwFlags = (uint)(REOOBJECTFLAGS.REO_BELOWBASELINE);
				reoObject.dwUser = 0;

				this.IRichEditOle.InsertObject(reoObject);
				//-----------------------

				//-----------------------
				Marshal.ReleaseComObject(pLockBytes);
				Marshal.ReleaseComObject(pOleClientSite);
				Marshal.ReleaseComObject(pStorage);
				Marshal.ReleaseComObject(pOleObject);
				//-----------------------
			}

			public void InsertOleObject(IOleObject oleObject)
			{
				if (oleObject == null)
					return;

				//-----------------------
				ILockBytes pLockBytes;
				Object pObject;

				CreateILockBytesOnHGlobal(IntPtr.Zero, true, out pObject);
				pLockBytes = pObject as ILockBytes;

				IStorage pStorage;
				StgCreateDocfileOnILockBytes(pLockBytes, (uint)(STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE | STGM.STGM_READWRITE), 0, out pStorage);

				IOleClientSite pOleClientSite;
				this.IRichEditOle.GetClientSite(out pOleClientSite);
				//-----------------------

				//-----------------------
				Guid guid = new Guid();

				oleObject.GetUserClassID(ref guid);
				//-----------------------

				//-----------------------
				OleSetContainedObject(oleObject, true);

				REOBJECT reoObject = new REOBJECT();

				reoObject.cp = this._richEdit.TextLength;

				reoObject.clsid = guid;
				reoObject.pstg = pStorage;
				reoObject.poleobj = Marshal.GetIUnknownForObject(oleObject);
				reoObject.polesite = pOleClientSite;
				reoObject.dvAspect = (uint)DVASPECT.DVASPECT_CONTENT;
				reoObject.dwFlags = (uint)REOOBJECTFLAGS.REO_BELOWBASELINE;

				this.IRichEditOle.InsertObject(reoObject);
				//-----------------------

				//-----------------------
				Marshal.ReleaseComObject(pLockBytes);
				Marshal.ReleaseComObject(pOleClientSite);
				Marshal.ReleaseComObject(pStorage);
				//-----------------------
			}

			public void UpdateObjects()
			{
				int k = this.IRichEditOle.GetObjectCount();

				for (int i = 0; i < k; i++)
				{
					REOBJECT reoObject = new REOBJECT();

					this.IRichEditOle.GetObject(i, reoObject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);

					if (reoObject.dwUser == 1)
					{
						Point pt = this._richEdit.GetPositionFromCharIndex(reoObject.cp);
						Rectangle rect = new Rectangle(pt, reoObject.sizel);

						this._richEdit.Invalidate(rect, false); // repaint
					}
				}
			}

		}

		#endregion

	}
}
